package com.todo.biz.application.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.api.client.util.Lists;
import com.todo.biz.application.convert.DictConvert;
import com.todo.biz.application.entity.bo.BaseDictBO;
import com.todo.biz.application.service.BaseDictService;
import com.todo.biz.common.base.DictKeyBuild;
import com.todo.biz.infrastructure.entity.po.BaseDictPO;
import com.todo.biz.infrastructure.repository.BaseDictRepository;
import com.todo.common.common.enums.BizErrorCodeEnum;
import com.todo.common.common.exception.BizException;
import com.todo.component.redis.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author azhebuxing
 * @date 2024/12/9 15:38
 * @description
 */
@Slf4j
@Service
public class BaseDictServiceImpl implements BaseDictService {

    /**
     * 默认使用Caffeine 本地缓存
     * 注：初期暂不考虑冷启动问题造成的缓存穿透
     */
    public static final Cache<String, BaseDictBO> DICT_LOCAL_CACHE = Caffeine.newBuilder()
            .maximumSize(1000) // 设置缓存最大容量为1000
            .expireAfterWrite(36, TimeUnit.HOURS) // 设置缓存过期时间为10分钟
            .build();

    @Resource
    private BaseDictRepository baseDictRepository;
    @Resource
    private RedisService redisService;


    @Override
    public Boolean insertBaseDict(BaseDictBO baseDictBO) {
        if (!baseDictBO.baseCheck()) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "参数异常");
        }
        BaseDictBO alreadyExist = queryBaseDictByBizCodeAndSceneCode(baseDictBO.getBizCode(), baseDictBO.getSceneCode());
        if (alreadyExist != null) {
            throw new BizException(BizErrorCodeEnum.SUCCESS_EXIST, "字典已存在，如需调整请更新数据");
        }

        BaseDictPO baseDictPO = DictConvert.convertToBaseDictPO(baseDictBO);
        Boolean inserted = baseDictRepository.insertBaseDict(baseDictPO);
        if (inserted) {
            String dictKey = DictKeyBuild.localCacheKey(baseDictPO.getBizCode(), baseDictPO.getSceneCode());
            DICT_LOCAL_CACHE.put(dictKey, baseDictBO);
            String redisKey = DictKeyBuild.buildDictCodeKey();
            String hashKey = DictKeyBuild.buildDictHashKey(baseDictPO.getBizCode(), baseDictPO.getSceneCode());
            redisService.hSet(redisKey, hashKey, JSONObject.toJSONString(baseDictBO));
        }
        log.info("BaseDictService#insertBaseDict 新增字典信息完成 入参：{}，结果：{}", JSONObject.toJSONString(baseDictBO), inserted);
        return inserted;
    }

    @Override
    public Boolean removeBaseDictById(Long id) {
        if (id == null || id < 0) {
            return true;
        }
        BaseDictBO baseDictBO = queryBaseDictById(id);
        if (baseDictBO == null) {
            return true;
        }
        Boolean deleted = baseDictRepository.removeBaseDictPOById(id);
        if (deleted) {
            String dictKey = DictKeyBuild.localCacheKey(baseDictBO.getBizCode(), baseDictBO.getSceneCode());
            DICT_LOCAL_CACHE.invalidate(dictKey);
            String redisKey = DictKeyBuild.buildDictCodeKey();
            String hashKey = DictKeyBuild.buildDictHashKey(baseDictBO.getBizCode(), baseDictBO.getSceneCode());
            redisService.hDel(redisKey, hashKey);
        }
        log.info("BaseDictService#removeBaseDictById 删除字典信息完成 入参：{}，结果：{}", id, deleted);
        return deleted;
    }

    @Override
    public Boolean updateBaseDictById(BaseDictBO baseDictBO) {
        if (baseDictBO == null || baseDictBO.getId() == null) {
            return true;
        }
        BaseDictBO already = queryBaseDictById(baseDictBO.getId());
        if (already == null) {
            throw new BizException(BizErrorCodeEnum.DATA_NOT_EXIST);
        }
        if (!already.getBizCode().equals(baseDictBO.getBizCode()) || !already.getSceneCode().equals(baseDictBO.getSceneCode())) {
            throw new BizException(BizErrorCodeEnum.ILLEGAL_OPERATION, "业务编码或场景编码不允许修改");
        }
        BaseDictPO baseDictPO = DictConvert.convertToBaseDictPO(baseDictBO);
        Boolean updated = baseDictRepository.updateBaseDictPOById(baseDictPO);
        if (updated) {
            String dictKey = DictKeyBuild.localCacheKey(baseDictBO.getBizCode(), baseDictBO.getSceneCode());
            DICT_LOCAL_CACHE.put(dictKey, baseDictBO);
            String redisKey = DictKeyBuild.buildDictCodeKey();
            String hashKey = DictKeyBuild.buildDictHashKey(baseDictBO.getBizCode(), baseDictBO.getSceneCode());
            redisService.hSet(redisKey, hashKey, JSONObject.toJSONString(baseDictBO));
        }
        log.info("BaseDictService#updateBaseDictById 更新字典信息完成 入参：{}，结果：{}", JSONObject.toJSONString(baseDictBO), updated);
        return updated;
    }

    @Override
    public BaseDictBO queryBaseDictById(Long id) {
        if (id == null || id < 0) {
            return null;
        }
        BaseDictPO baseDictPO = baseDictRepository.queryBaseDictPOById(id);
        log.info("BaseDictService#queryBaseDictById 查询字典信息完成 入参：{}，结果：{}", id, JSONObject.toJSONString(baseDictPO));
        return DictConvert.convertToBaseDictBO(baseDictPO);
    }

    @Override
    public BaseDictBO queryBaseDictByBizCodeAndSceneCode(String bizCode, String sceneCode) {
        if (bizCode == null || sceneCode == null) {
            return null;
        }
        String localCacheKey = DictKeyBuild.localCacheKey(bizCode, sceneCode);
        BaseDictBO ifPresent = DICT_LOCAL_CACHE.getIfPresent(localCacheKey);
        if (ifPresent != null) {
            return ifPresent;
        }
        String redisKey = DictKeyBuild.buildDictCodeKey();
        String redisHashKey = DictKeyBuild.buildDictHashKey(bizCode, sceneCode);
        BaseDictBO redisData = JSONObject.parseObject(redisService.hGet(redisKey, redisHashKey), BaseDictBO.class);
        if (redisData != null) {
            DICT_LOCAL_CACHE.put(localCacheKey, redisData);
            return redisData;
        }
        BaseDictPO baseDictPO = baseDictRepository.queryBaseDictByBizCodeAndSceneCode(bizCode, sceneCode);
        if (baseDictPO == null) {
            return null;
        }
        BaseDictBO baseDictBO = DictConvert.convertToBaseDictBO(baseDictPO);
        redisService.hSet(redisKey, redisHashKey, JSONObject.toJSONString(baseDictBO));
        DICT_LOCAL_CACHE.put(localCacheKey, baseDictBO);
        log.info("BaseDictService#queryBaseDictByBizCodeAndSceneCode 查询字典信息完成 入参：{},结果：{}", redisHashKey, JSONObject.toJSONString(baseDictBO));
        return baseDictBO;
    }

    @Override
    public List<BaseDictBO> queryBaseDictListByCache() {
        String redisKey = DictKeyBuild.buildDictCodeKey();
        Map<String, String> cacheMap = redisService.hGetAll(redisKey);
        if (MapUtils.isEmpty(cacheMap)) {
            return queryDictListAndResetCache();
        }
        List<BaseDictBO> baseDictBOList = Lists.newArrayList();
        for (String key : cacheMap.keySet()) {
            BaseDictBO baseDictBO = JSONObject.parseObject(cacheMap.get(key), BaseDictBO.class);
            baseDictBOList.add(baseDictBO);
        }
        return baseDictBOList;
    }

    @Override
    public List<BaseDictBO> queryDictListAndResetCache() {
        String redisKey = DictKeyBuild.buildDictCodeKey();
        redisService.del(redisKey);
        List<BaseDictPO> baseDictPOList = baseDictRepository.queryBaseDictList();
        if (CollectionUtils.isEmpty(baseDictPOList)) {
            return Lists.newArrayList();
        }
        List<BaseDictBO> collect = baseDictPOList.stream().map(DictConvert::convertToBaseDictBO).collect(Collectors.toList());
        for (BaseDictBO baseDictBO : collect) {
            String hashKey = DictKeyBuild.buildDictHashKey(baseDictBO.getBizCode(), baseDictBO.getSceneCode());
            redisService.hSet(redisKey, hashKey, JSONObject.toJSONString(baseDictBO));
        }
        return collect;
    }
}
